/**
 * This file is released under the GNU General Public License.
 * Refer to the COPYING file distributed with this package.
 *
 * Copyright (c) 2008-2009 WURFL-Pro srl
 */
package net.sourceforge.wurfl.core.resource;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sourceforge.wurfl.core.Constants;

import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This static class verifies the Model consistency
 * 
 * @author WURFL-PRO SRL, Rome, Italy
 * @version $Id: WURFLConsistencyVerifier.java 1045 2009-03-09 15:52:53Z filippo.deluca $
 */
public class WURFLConsistencyVerifier {

	/** Log */
	private static final Log log = LogFactory
			.getLog(WURFLConsistencyVerifier.class);

	/**
	 * Verify devices collection consistency.
	 * 
	 * @param devices
	 *            The ModelDevices to verify.
	 * @throws WURFLConsistencyException
	 *             If the given ModelDevices is not consistent.
	 */
	public static void verify(ModelDevices devices)
			throws WURFLConsistencyException {

		Map userAgents = new HashMap();
		Set hierarchyVerifiedDevices = new HashSet();

		log.debug("Verifing generic existence...");
		verifyGeneric(devices);

		Iterator devicesIterator = devices.iterator();
		while (devicesIterator.hasNext()) {

			ModelDevice device = (ModelDevice) devicesIterator.next();

			if (log.isDebugEnabled()) {
				log.debug("Verifing device: " + device.getID());
			}

			if (log.isTraceEnabled()) {
				log.trace("Verifing " + device.getID() + " userAgent...");
			}
			verifyUserAgent(device, userAgents);
			userAgents.put(device.getUserAgent(), device);

			if (log.isTraceEnabled()) {
				log.trace("Verifing " + device.getID() + " hierarchy...");
			}
			verifyHierarchy(device, devices, hierarchyVerifiedDevices);
			hierarchyVerifiedDevices.add(device.getID());

			if (log.isTraceEnabled()) {
				log.trace("Verifing " + device.getID() + " groups...");
			}
			verifyGroups(device, devices);

			if (log.isTraceEnabled()) {
				log.trace("Verifing " + device.getID() + " capabilities...");
			}

			verifyCapabilities(device, devices);
		}

	}

	private static void verifyGeneric(ModelDevices devices)
			throws WURFLConsistencyException {

		assert devices != null : "devices is null";

		if (!devices.containsId(Constants.GENERIC)) {
			throw new GenericNotDefinedException();
		}
	}

	private static void verifyUserAgent(ModelDevice device, Map userAgents)
			throws UserAgentConsistencyException {

		assert device != null : "device is null";
		assert userAgents != null : "userAgents is null";

		if (userAgents.containsKey(device.getUserAgent())) {

			ModelDevice definingDevice = (ModelDevice) userAgents.get(device
					.getUserAgent());

			throw new UserAgentNotUniqueException(device,
					device.getUserAgent(), definingDevice);

		}
	}

	private static void verifyHierarchy(ModelDevice device,
			ModelDevices devices, Set hierarchyVerifiedDevicesId)
			throws HierarchyConsistencyException {

		assert device != null : "device is null";
		assert devices != null : "devices is null";
		assert hierarchyVerifiedDevicesId != null : "hierarchyVerifiedDevicesId is null";

		List hierarchy = new ArrayList(10);

		String deviceId = device.getID();
		assert !StringUtils.isEmpty(deviceId);

		hierarchy.add(deviceId);

		while (!Constants.GENERIC.equals(deviceId)) {

			ModelDevice examineDevice = devices.getById(deviceId);

			String fallbackId = examineDevice.getFallBack();

			// Not-Null verified bt ResourceHandler
			assert !StringUtils.isEmpty(fallbackId);

			if (hierarchyVerifiedDevicesId.contains(fallbackId)) {
				// OK
				return;
			}

			if (!devices.containsId(fallbackId)) {
				throw new OrphanHierarchyException(hierarchy);
			}

			int hierarchyIndex = hierarchy.indexOf(fallbackId);
			if (hierarchyIndex != -1) {

				List circularHierarchy = new LinkedList(hierarchy.subList(
						hierarchyIndex, hierarchy.size()));
				throw new CircularHierarchyException(circularHierarchy);
			}

			hierarchy.add(fallbackId);

			deviceId = fallbackId;

		}

	}

	private static void verifyGroups(ModelDevice device, ModelDevices devices)
			throws GroupConsistencyException {

		assert device != null : "device is null";
		assert devices != null : "devices is null";

		ModelDevice generic = devices.getById(Constants.GENERIC);

		Set deviceGroups = device.getGroups();
		Set genericGroups = generic.getGroups();

		for (Iterator gIt = deviceGroups.iterator(); gIt.hasNext();) {
			String group = (String) gIt.next();

			if (!genericGroups.contains(group)) {
				throw new InexistentGroupException(device, group);
			}

		}

	}

	private static void verifyCapabilities(ModelDevice device,
			ModelDevices devices) throws CapabilityConsistencyException {

		assert device != null : "device is null";
		assert devices != null : "devices is null";
		assert devices.containsId(Constants.GENERIC) : "device do not containing "
				+ Constants.GENERIC;

		ModelDevice generic = devices.getById(Constants.GENERIC);

		Map genericCapabilities = generic.getCapabilities();
		Map deviceCapabilities = device.getCapabilities();

		for (Iterator dcnIt = deviceCapabilities.keySet().iterator(); dcnIt
				.hasNext();) {

			String capabilityName = (String) dcnIt.next();

			if (log.isTraceEnabled()) {
				log.trace("Verifing device: " + device.getID()
						+ " capability: " + capabilityName);
			}

			if (!genericCapabilities.containsKey(capabilityName)) {
				throw new InexistentCapabilityException(device, capabilityName);
			}

			if (!(device.getGroupForCapability(capabilityName).equals(generic
					.getGroupForCapability(capabilityName)))) {

				throw new BadCapabilityGroupException(device, capabilityName,
						device.getGroupForCapability(capabilityName), generic
								.getGroupForCapability(capabilityName));
			}
		}

	}

	static class EqualsDeviceIdPredicate implements Predicate {

		private String id;

		public EqualsDeviceIdPredicate(String id) {
			this.id = id;
		}

		public boolean evaluate(Object object) {
			ModelDevice other = (ModelDevice) object;

			return id.equals(other.getID());
		}

	}

}
